﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using ProgNetComponents;
using ProgNetComponents.Data;
using ProgNetComponents.Classes;
using ProgNetComponentsDx.Enums;
using ProgNetComponents.Interfaces;
using System.Collections;
using ProgNetComponents.Collections;

namespace ProgNetComponentsDx.Browsers
{
    public static class BrowserCache
    {
        public static event BrowserHandler BeforeBrowserGetData;
        public static event BrowserHandler BeforeBrowserShowDialog;
        public static event BrowserHandler AfterBrowserRefreshData;
        public static event GetStaticBrowserDataDelegate GetStaticData;

        private static bool inBeforeBrowserGetData = false;
        private static bool inAfterBrowserRefreshData = false;

        internal static void OnBeforeBrowserShowDialog(object browserId)
        {
            if (BeforeBrowserShowDialog != null)
            {
                BeforeBrowserShowDialog(new BrowserEventArgs(browserId));
            }
        }
        private static void OnBeforeBrowserGetData(object browserId)
        {
            if (inBeforeBrowserGetData)
                return;
            if (BeforeBrowserGetData != null)
            {
                inBeforeBrowserGetData = true;
                try
                {
                    BeforeBrowserGetData(new BrowserEventArgs(browserId));
                }
                finally
                {
                    inBeforeBrowserGetData = false;
                }
            }
        }
        private static void OnAfterBrowserRefreshData(object browserId)
        {
            if (inAfterBrowserRefreshData)
                return;
            if (AfterBrowserRefreshData != null)
            {
                inAfterBrowserRefreshData = true;
                try
                {
                    AfterBrowserRefreshData(new BrowserEventArgs(browserId));
                }
                finally
                {
                    inAfterBrowserRefreshData = false;
                }
            }
        }

        public static void TestBrowserLng()
        {
            Browser b;
            foreach (var i in RegisteredBrowsers)
            {
                if (i.Value is Browser)
                {
                    b = i.Value as Browser;
                    Language.ProgNetLanguage.sGetString(b.Name);
                    foreach (BrowserColumn c in b.Columns)
                    {
                        if (c.Caption.IsNull())
                            Language.ProgNetLanguage.sGetString(c.Name);
                    }
                }
            }
        }

        static ProgNetComponentsDx.Layout.FormLayoutSettings _DefaultBrowserFormLayoutSettings = null;
        public static ProgNetComponentsDx.Layout.FormLayoutSettings DefaultBrowserFormLayoutSettings
        {
            get
            {
                if (_DefaultBrowserFormLayoutSettings == null)
                {
                    _DefaultBrowserFormLayoutSettings = new Layout.FormLayoutSettings(null, null);
                    _DefaultBrowserFormLayoutSettings.SavePosition = false;
                }
                return _DefaultBrowserFormLayoutSettings;
            }
        }
        public static bool SaveLayouts
        {
            get;
            set;
        }

        private static NamedObjectCollection _RegisteredBrowsers = new NamedObjectCollection();
        public static NamedObjectCollection RegisteredBrowsers
        {
            get { return _RegisteredBrowsers; }
        }

        private static BrowserConfigRecordCollection ConfigCollection = new BrowserConfigRecordCollection();

        public static BrowserConfigRecord[] GetConfigRecords()
        {
            return ConfigCollection.ToArray();
        }
        public static bool HasConfig(string groupName)
        {
            return ConfigCollection.Contains(groupName);
        }
        public static BrowserDisplayType GetBrowserDisplayType(Browser b)
        {
            BrowserDisplayType res = b.DisplayType;
            if (b.Group.IsNotNull() && HasConfig(b.Group))
            {
                BrowserConfigDisplayType d = ConfigCollection[b.Group].DisplayType;
                switch (d)
                {
                    case BrowserConfigDisplayType.Auto:
                        if (GetBrowserData(b.Name).Rows.Count >= ConfigCollection[b.Group].MinRecordsForDialog)
                            return BrowserDisplayType.DialogWindow;
                        else
                            return BrowserDisplayType.DropDown;
                    case BrowserConfigDisplayType.ComboDropDown:
                        return BrowserDisplayType.ComboDropDown;
                    case BrowserConfigDisplayType.DialogWindow:
                        return BrowserDisplayType.DialogWindow;
                    case BrowserConfigDisplayType.DropDown:
                        return BrowserDisplayType.DropDown;
                }
            }
            if (res == BrowserDisplayType.ConfigSpecified)
                res = BrowserDisplayType.DropDown;
            return res;
        }

        public static void SetConfig(DataTable dt, string GroupNameColumn, string DisplayTypeColumn, string MinRecordsForDialogColumn)
        {
            string name = "";
            foreach (DataRow row in dt.Rows)
            {
                name = row[GroupNameColumn].AsString();
                if (ConfigCollection.Contains(name))
                {
                    BrowserConfigRecord rec = ConfigCollection[name];
                    rec.DisplayType = (BrowserConfigDisplayType)(Enum.Parse(typeof(BrowserConfigDisplayType), row[DisplayTypeColumn].AsString()));
                    rec.MinRecordsForDialog = row[MinRecordsForDialogColumn].AsInt();
                }
            }
        }

        public static void ReadConfig(string selectString, string GroupNameColumn, string DisplayTypeColumn, string MinRecordsForDialogColumn)
        {
            DataTable dt = new DataTable("ConfigTable");
            DataManager.FillTable(dt, selectString);
            SetConfig(dt, GroupNameColumn, DisplayTypeColumn, MinRecordsForDialogColumn);
        }


        private static Dictionary<string, DataTable> AllData = new Dictionary<string, DataTable>();
        private static Dictionary<string, List<object>> BrowsersByTable = new Dictionary<string, List<object>>();

        public static DataManager DataManager
        {
            get;
            set;
        }

        public static void RegisterGroups(BrowserGroupCollection groups, Type namesType)
        {
            string key = namesType.FullName + ".";
            foreach (INamedGroup g in groups)
            {
                RegisterGroups(g, key + g.Name);
            }
        }
        public static string GetKey(object browserId)
        {
            string key = "";
            if (browserId != null && browserId.GetType().IsEnum)
            {
                key = browserId.GetType().FullName;
                key = key.Replace("+", ".").XReplace("_Group\\.", ".") + ".";
            }
            key += browserId.AsString();
            return key;
        }

        private static void RegisterGroups(INamedGroup group, string key)
        {
            RegisterItems(group.Items, key);
            foreach (INamedGroup g in group.Groups)
            {
                RegisterGroups(g, key + "." + g.Name);
            }
        }
        private static void RegisterItems(IList items, string key)
        {
            Browser b;
            foreach (INamedObject o in items)
            {
                b = o as Browser;
                b.BrowserId = key + "." + o.Name;
                RegisterBrowser(b);
            }
        }
        public static void RegisterBrowser(Browser b)
        {
            if (!b.IsStatic)
            {
                List<object> list;
                string tab;
                foreach (string table in b.Tables)
                {
                    tab = table.ToUpper();
                    if (BrowsersByTable.ContainsKey(tab))
                        list = BrowsersByTable[tab];
                    else
                    {
                        list = new List<object>();
                        BrowsersByTable.Add(tab, list);
                    }
                    list.AddIfNotExist(b.BrowserId);
                }
            }
            RegisteredBrowsers.Add(b.BrowserId.AsString(), b);
        }
        //public static void RegisterBrowser(Browser browser)
        //{
        //    browser.Name = browser.Name;
        //    AllBrowsers.Add(browser);
        //    if (browser.Group.IsNotNull())
        //    {
        //        if (!ConfigCollection.Contains(browser.Group))
        //        {
        //            BrowserConfigRecord r = new BrowserConfigRecord();
        //            r.Name = browser.Group;
        //            r.DisplayType = browser.DefaultConfigDisplayType;
        //            ConfigCollection.Add(r);
        //        }
        //    }
        //    if (!browser.IsStatic)
        //    {
        //        if (browser.Tables != null)
        //        {
        //            foreach (string t in browser.Tables)
        //            {
        //                string table = t.ToUpper();
        //                List<string> browsers = null;
        //                if (BrowsersByTable.ContainsKey(table))
        //                    browsers = BrowsersByTable[table];
        //                else
        //                {
        //                    browsers = new List<string>();
        //                    BrowsersByTable.Add(table, browsers);
        //                }
        //                browsers.Add(browser.Name);
        //            }
        //        }
        //    }
        //}

        public static Browser GetBrowser(object browserId, bool clone = true)
        {
            string name = GetKey(browserId);
            if (name.IsNotNull() && RegisteredBrowsers.Contains(name))
            {
                Browser b = (RegisteredBrowsers[name].Value as Browser);
                if (clone)
                    b = b.Clone() as Browser;
                return b;
            }
            return null;
        }

        public static ObjectDialogResult ShowBrowserDialog(object browserId, bool allowNew = false)
        {
            if (browserId.IsNotNull())
            {
                string key = GetKey(browserId);
                if (RegisteredBrowsers.Contains(key))
                {
                    return (RegisteredBrowsers[key].Value as Browser).ShowDialog(null, allowNew);
                }
            }
            return null;
        }

        public static DataTable GetFullData(object browserId, bool refresh = false)
        {
            lock (DataManager)
            {
                Browser b = GetBrowser(browserId);
                string browserName = GetKey(browserId);
                if (b.IsStatic)
                {
                    return GetStaticTable(b);
                }
                else if (!AllData.ContainsKey(browserName))
                {
                    DataTable dt = new DataTable(browserName);
                    LockBrowser(browserId);
                    try
                    {
                        DataManager.FillTable(dt, GetSelectString(b));
                        AllData.Add(browserName, dt);
                        OnAfterBrowserRefreshData(browserId);
                    }
                    finally
                    {
                        UnLockBrowser(browserId);
                    }
                    return dt;
                }
                else
                {
                    if (AllData[browserName].Rows.Count == 0 && refresh)
                    {
                        if (!IsBrowserLocked(browserId))
                        {
                            LockBrowser(browserId);
                            try
                            {
                                DataManager.FillTable(AllData[browserName], GetSelectString(b));
                                OnAfterBrowserRefreshData(browserId);
                            }
                            finally
                            {
                                UnLockBrowser(browserId);
                            }
                        }
                    }
                    return AllData[browserName];
                }
            }
        }

        private static string GetSelectString(Browser b)
        {
            return GetSelectString(b, "");
        }

        private static string GetSelectString(Browser b, string filter)
        {
            string columns = "";
            string tables = "";
            string where = "";
            if (b.CustomColumnQuery.IsNotNull())
            {
                columns = b.CustomColumnQuery;
            }
            else
            {
                foreach (BrowserColumn c in b.Columns)
                {
                    columns = columns.AppendString(", ");
                    if (c.Expression.IsNotNull())
                        columns += "(" + c.Expression + ") AS ";
                    columns += c.Name.ToUpper();
                }
                columns = " DISTINCT " + columns;
            }
            if (b.CustomFromQuery.IsNotNull())
            {
                tables = b.CustomFromQuery;
            }
            else
            {
                foreach (string t in b.Tables)
                {
                    tables = tables.AppendString(", ");
                    tables += t;
                }
            }
            string select = "SELECT " + columns + " FROM " + tables;
            if (b.CustomFilterQuery.IsNotNull())
            {
                where = "(" + b.CustomFilterQuery + ")";
            }
            else
            {
                if (b.Filter.IsNotNull())
                    where = "(" + b.Filter + ")";
            }
            if (filter.IsNotNull())
            {
                if (where.IsNotNull())
                    where += " AND ";
                where += " ( " + where + " ) ";
            }
            if (where.IsNotNull())
                select += " WHERE " + where;
            return select;
        }

        public static void RefreshCacheForBrowser(object browserId)
        {
            string browserName = GetKey(browserId);
            if (AllData.ContainsKey(browserName))
            {
                AllData[browserName].Clear();
                AllData[browserName].AcceptChanges();
                GetBrowserData(browserId, true);
            }
        }

        private static List<object> lockedBrowsers = new List<object>();
        public static void LockBrowser(object browserId)
        {
            lockedBrowsers.AddIfNotExist(GetKey(browserId));
        }
        public static void UnLockBrowser(object browserId)
        {
            lockedBrowsers.Remove(GetKey(browserId));
        }
        public static bool IsBrowserLocked(object browserId)
        {
            return lockedBrowsers.Contains(GetKey(browserId));
        }
        public static void RefreshCache(DataSet ds)
        {
            foreach (DataTable dt in ds.Tables)
            {
                RefreshCacheForTable(dt.TableName);
            }
        }

        public static void RefreshCacheForTable(string tableName)
        {
            tableName = tableName.ToUpper();
            List<object> browsers = new List<object>();
            if (BrowsersByTable.ContainsKey(tableName))
            {
                foreach (object browserId in BrowsersByTable[tableName])
                {
                    if (!browsers.Contains(browserId))
                        browsers.Add(browserId);
                }
            }
            foreach (object b in browsers)
                RefreshCacheForBrowser(b);
        }

        //public static void RefreshAllCache()
        //{
        //    object[] browsers = AllData.Keys.ToArray();
        //    foreach (string b in browsers)
        //        RefreshCacheForBrowser(b);
        //}

        public static DataTable GetBrowserData(object browserId, bool refresh = true)
        {
            DataTable dt = null;
            Browser b = GetBrowser(browserId, false);
            OnBeforeBrowserGetData(browserId);
            if (b != null)
            {
                if (b.IsStatic)
                    dt = GetStaticTable(b, refresh);
                else
                    dt = GetFullData(browserId, refresh);
            }
            else
            {
                ("Invalid browser:" + browserId).LogInternal();
            }
            return dt;
        }
        public static bool IsTheSameBrowser(object browserId1, object browserId2)
        {
            return GetKey(browserId1) == GetKey(browserId2);
        }

        private static DataTable GetStaticTable(Browser b, bool refresh = false)
        {
            DataTable dt = null;
            string key = GetKey(b.BrowserId);
            if (!AllData.ContainsKey(key) || b.RefreshOnPopup || refresh)
            {
                if (GetStaticData != null)
                {
                    dt = GetStaticData(b.BrowserId);
                    if (!AllData.ContainsKey(key))
                    {
                        AllData.Add(key, dt);
                    }
                    else
                    {
                        AllData[key].Clear();
                        AllData[key].Merge(dt);
                        dt = AllData[key];
                    }
                }
            }
            else
            {
                dt = AllData[key];
            }
            return dt;
        }

        private static object GetId(object browserId, object currentId, bool next)
        {
            if (browserId.IsNotNull() && currentId.IsNotNull())
            {
                Browser b = GetBrowser(browserId, false);
                if (b != null)
                {
                    DataTable d = GetBrowserData(browserId);
                    string col = b.ValueMember;
                    DataRow r = d.SelectFirstRow(col + " = " + currentId.AsString().EscapeSql());
                    if (r != null)
                    {
                        int idx = d.Rows.IndexOf(r);
                        if (next)
                            idx++;
                        else
                            idx--;
                        if (idx >= 0 && idx < d.Rows.Count)
                            return d.Rows[idx][col];
                    }
                }
            }
            return null;
        }

        public static object GetFirstId(object browserId)
        {
            Browser b = GetBrowser(browserId, false);
            if (b != null)
            {
                DataTable d = GetBrowserData(browserId);

                if (d.Rows.Count > 0)
                {
                    return d.Rows[0][b.ValueMember];
                }
            }
            return null;
        }
        public static object GetLastId(object browserId)
        {
            Browser b = GetBrowser(browserId, false);
            if (b != null)
            {
                DataTable d = GetBrowserData(browserId);

                if (d.Rows.Count > 0)
                {
                    return d.Rows[d.Rows.Count - 1][b.ValueMember];
                }
            }
            return null;
        }
        public static object GetNextId(object browserId, object currentId)
        {
            return GetId(browserId, currentId, true);
        }
        public static object GetPrevId(object browserId, object currentId)
        {
            return GetId(browserId, currentId, false);
        }

        public static DataRow GetRowForValue(object browserId, object value, string filter = null)
        {
            DataTable dt = GetBrowserData(browserId);
            Browser b = GetBrowser(browserId);
            if (dt != null && b != null)
            {
                string s = "";
                if (filter.IsNotNull())
                    s = "(" + filter + ")";
                s = s.AppendAnd();
                if (value.IsNotNull())
                    s += b.ValueMember + " = " + value.AsString().EscapeSql();
                else
                {
                    if (dt.Columns.Contains(b.ValueMember) && dt.Columns[b.ValueMember].DataType == typeof(string))
                        s += "(" + b.ValueMember + " IS NULL OR " + b.ValueMember + " = '')";
                    else
                        s += b.ValueMember + " IS NULL";
                }

                return dt.SelectFirstRow(s);
            }
            return null;
        }

        public static object GetColumnValueForEditValue(object browserId, object value, string column, string filter = null)
        {
            object str = null;
            DataRow r = GetRowForValue(browserId, value, filter);
            if (r != null)
            {
                str = r[column];
            }
            return str;
        }
        public static object GetDisplayForValue(object browserId, object value, string filter = null)
        {
            object str = null;

            DataRow r = GetRowForValue(browserId, value, filter);
            if (r != null)
            {
                Browser b = GetBrowser(browserId, false);
                str = r[b.DisplayMember];
            }
            return str;
        }
        public static object GetDisplayForValues(object browserId, object[] values, string filter = null, string separator = ", ")
        {
            string str = string.Empty;
            foreach (object value in values)
            {
                str = str.AppendString(separator);
                str += GetDisplayForValue(browserId, value, filter);
            }
            return str;
        }

        /// <summary>
        /// Check if Browser has Row for whitch Display value = Value. if Value is null returns nullIsValid
        /// </summary>
        /// <param name="value"></param>
        /// <param name="nullIsValid"></param>
        /// <param name="browserId"></param>
        /// <param name="filter"></param>
        /// <returns></returns>
        public static bool IsValidDisplayMember(object browserId, bool nullIsValid, object value, string filter = null)
        {
            if (nullIsValid && value.IsNull())
                return true;

            DataTable dt = GetBrowserData(browserId);
            Browser b = GetBrowser(browserId);
            if (dt != null && b != null)
            {
                string s = "";
                if (filter.IsNotNull())
                    s = "(" + filter + ")";
                s = s.AppendAnd();
                s += b.DisplayMember + " = " + value.AsString().EscapeSql();
                return dt.RowExists(s);
            }
            return false;
        }

        /// <summary>
        /// Check if Browser has Row for Value. if Value is null returns nullIsValid
        /// </summary>
        /// <param name="value"></param>
        /// <param name="nullIsValid"></param>
        /// <param name="browserId"></param>
        /// <param name="filter"></param>
        /// <returns></returns>
        public static bool IsValid(object browserId, bool nullIsValid, object value, string filter = null)
        {
            if (nullIsValid && value.IsNull())
                return true;

            DataTable dt = GetBrowserData(browserId, false);
            Browser b = GetBrowser(browserId);
            if (dt != null && b != null)
            {
                string s = "";
                if (filter.IsNotNull())
                    s = "(" + filter + ")";
                s = s.AppendAnd();
                s += b.ValueMember + " = " + value.AsString().EscapeSql();
                return dt.RowExists(s);
            }
            return false;
        }
        /// <summary>
        /// Checks every row if value of column is valid. If not and errorMsg is not null, sets column error
        /// </summary>
        /// <param name="browserId"></param>
        /// <param name="nullIsValid"></param>
        /// <param name="column"></param>
        /// <param name="errorMsg"></param>
        /// <param name="filter"></param>
        /// <param name="rows"></param>
        /// <returns></returns>
        public static bool IsValid(object browserId, bool nullIsValid, string column, string errorMsg, string filter = null, params DataRow[] rows)
        {
            DataTable dt = GetBrowserData(browserId);
            Browser b = GetBrowser(browserId);
            bool res = true;
            if (dt != null && b != null)
            {
                object value;
                string s = "";
                if (filter.IsNotNull())
                    s = "(" + filter + ")";
                s = s.AppendAnd();
                s += b.ValueMember + " = ";

                foreach (DataRow r in rows)
                {
                    value = r[column];
                    if (dt.RowExists(s + value.AsString().EscapeSql()))
                    {
                        res = false;
                        r.SetColumnError(column, errorMsg);
                    }
                }
            }
            else
                res = false;
            return res;
        }
        /// <summary>
        /// Checks every row from table (selected for filter) if value of column is valid. If not and errorMsg is not null, sets column error
        /// </summary>
        /// <param name="browserId"></param>
        /// <param name="nullIsValid"></param>
        /// <param name="column"></param>
        /// <param name="errorMsg"></param>
        /// <param name="dt"></param>
        /// <param name="filter"></param>
        /// <returns></returns>
        public static bool IsValid(object browserId, bool nullIsValid, string column, string errorMsg, DataTable dt, string filter = null)
        {
            return IsValid(browserId, nullIsValid, column, errorMsg, filter, dt.Select(null, null, DataViewRowState.CurrentRows));
        }
    }
    public class BrowserEventArgs : EventArgs
    {
        public object BrowserId
        {
            get;
            private set;
        }
        public BrowserEventArgs(object browserId)
        {
            BrowserId = browserId;
        }
    }
    public delegate void BrowserHandler(BrowserEventArgs e);
    public delegate DataTable GetStaticBrowserDataDelegate(object browserId);
}
